fix(logging): stop leaking env values and secrets in messages, make context serialization non-throwing - #3325
Conversation
…ontext serialization non-throwing - env-loader: debug logging printed the first 20 chars of every env var value (including VERYFRONT_API_TOKEN) on the live bootstrap debug path; now logs only the key name and value length. The unconditional VERYFRONT_API_BASE_URL info log now strips userinfo credentials. - logger: the log message string reached JSON/text output verbatim, bypassing the #1989 redaction; it is now scrubbed with sanitizeUrlCredentials on both the JSON and text paths. - logger: JSON serialization of a log entry could throw out of the caller (BigInt context values, hostile toJSON); stringification now uses a BigInt-safe replacer with a fail-closed redacted fallback.
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
|
Warning Review limit reached
Next review available in: 54 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Plus Run ID: ⛔ Files ignored due to path filters (1)
📒 Files selected for processing (12)
Comment |
There was a problem hiding this comment.
Pull request overview
This PR hardens Veryfront’s logging and env-loading paths to prevent leaking secrets in log messages and to ensure JSON log emission never throws back into application call sites.
Changes:
- Stop leaking env var values in
loadEnvdebug logs, and scrub credentials from the loggedVERYFRONT_API_BASE_URL. - Scrub credential-shaped content embedded directly in the log message (JSON and text formats).
- Make JSON log serialization non-throwing via
stringifyLogEntry, handling BigInt and hostile/statefultoJSON.
Verification noted in PR description (not re-run here):
deno check(changed files): clean- Targeted
deno test ...for logger + env-loader: 3 passed (117 steps), 0 failed deno fmt/deno lint(changed files): clean
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated no comments.
| File | Description |
|---|---|
| src/utils/logger/logger.ts | Scrubs secrets in message strings and adds non-throwing JSON serialization for log entries. |
| src/utils/logger/logger.test.ts | Adds tests for message redaction (JSON/text) and non-throwing serialization (BigInt + hostile toJSON). |
| src/utils/env-loader.ts | Removes env value leakage from debug logs and sanitizes logged API base URLs. |
| src/utils/env-loader.test.ts | Adds regression tests to ensure env values and URL credentials are not emitted to logs. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
Merge confidence: 94%. Reasoning: exact head Residual risk: logging redaction remains pattern-based, so it cannot prove all future credential shapes are covered, but this PR improves the existing boundaries without widening runtime behavior. Confidence is above the 90% threshold, so I am scheduling this PR for merge with the exact head SHA guard. |
kwakayama
left a comment
There was a problem hiding this comment.
Review: 80/100 — one silent regression from merge-ready
| Axis | Score |
|---|---|
| Correctness | 32/40 |
| Test adequacy | 19/25 |
| Security / prod-safety | 16/20 |
| Maintainability | 13/15 |
| Total | 80/100 |
Head 4955b4cc2, merge base d63ea1b93. Three-dot throughout.
No leak is introduced and both claims hold up. The blocker is not a security issue — it is a silent log-data-loss regression, and it is easy to fix.
Claim 1 — secret redaction: real, with no bypass paths
The predicate is a 25-pattern denylist (redact.ts:161-187) matched as normalized substring (isSensitiveKey, :209 — lowercased, non-alphanumerics stripped), so CLIENT_SECRET, x-api-key, refreshToken, Authorization all match. I confirmed each by execution.
sanitizeUrlCredentials is much more than a URL scrubber — five stages (redact.ts:733): URL userinfo, sensitive query/fragment params, Cookie/Set-Cookie lines, authorization/Bearer/Basic, and generic key: value assignments in free text. So logger.info('{"apiKey":"sk-abc"}') and logger.info("token=xyz") are redacted. The inline comment understates this.
No bypass paths. Every emission route in logger.ts is covered: message → sanitizeUrlCredentials on both JSON (:429) and text (:555); context → redactSensitive (:535, :558); error → sanitizeSerializedError (:539, :559); lifted Loki fields → sanitizeStringFieldValue (:348). Nesting is handled to depth 16 / 1024 entries / 4096 nodes, failing closed to [REDACTED] on cycles, depth overflow, or throwing getters.
P2 — free-form messages are now silently over-redacted on ordinary English words
logger.ts:429, :555 → redact.ts:817 → isSensitiveKey
isSensitiveKey is SENSITIVE_KEY_PATTERNS.some(p => normalized.includes(p)) — raw substring. Applied to bare identifiers in free text, common words hit the denylist. Verified by execution:
mapping -> REDACTED (matches: pin)
spinner -> REDACTED (matches: pin)
pinned -> REDACTED (matches: pin)
considered -> REDACTED (matches: sid)
reside -> REDACTED (matches: sid)
residual -> REDACTED (matches: sid)
saltiness -> REDACTED (matches: salt)
Concrete: logger.info("mapping: 4 routes resolved") now emits mapping: [REDACTED].
This is new to this PR. isSensitiveKey previously ran only against structured context keys, where over-redaction is cheap and explicitly accepted — masking a benign tokenCount costs nothing. Applying the same substring predicate to every free-form message is a different trade: it destroys the message's information content and will break Loki queries and greps matching on message text.
Fix: for the free-text assignment stage only, require a word-boundary/full-token match (sid as a whole token, not inside considered). The URL-parameter and header stages can keep the loose predicate.
P2 — no value-based detection: a bare secret with no assignment syntax still leaks
redact.ts:733. Bearer /Basic prefixes are caught (stage 4); sk-, ghp_, xox…, and high-entropy strings are not — there is no entropy heuristic and no provider-prefix list anywhere in the file.
Surviving leak:
logger.info(`Using token ${apiToken}`); // "Using token sk-proj-abc123..."token is followed by a space, not : or =, so stage 5's \s*[:=]\s* never matches and the full token is emitted. Same for logger.debug(refreshToken) where the message is the secret.
A residual gap rather than a regression — but this is exactly the "partial redaction creates false confidence" shape, so it should be documented rather than implied away by the commit message. A \b(sk-|ghp_|gho_|xox[baprs]-|eyJ)[A-Za-z0-9._-]{8,} pass closes the common cases cheaply.
P3 — denylist misses bare auth and bare key
Verified by execution: auth and key both return not sensitive. { auth: "Bearer xyz" } is saved only by stage 4's value check; { auth: "<opaque>" } or { key: "sk-…" } is redacted by neither key nor value. Adding "auth" closes it; "key" would too, at the cost of more over-redaction.
Claim 2 — non-throwing context serialization: fixes a real live crash
redact.ts:272 returns BigInt unchanged in "compatible" mode, which is what redactSensitive uses. On main, formatJson was a bare JSON.stringify(entry) → TypeError: Do not know how to serialize a BigInt thrown out of the logger.info() call site. Any logger.info("…", { count: 42n }) crashed the caller. The new jsonSafeReplacer (logger.ts:356) fixes it.
It degrades per-field, not whole-context — the test asserts context.count === "42" and context.hostile === "[REDACTED]" in the same entry, so a hostile field is masked while siblings survive. That is the right shape, and notably avoids the failure mode seen in #3287 where a structured error collapsed wholesale.
The text path was already safe (core.ts:159 wrapped JSON.stringify with a String(value) fallback), so this claim is really scoped to the JSON path — accurate, just narrower than the title suggests.
P3s
- Fallback tiers 2 and 3 are untested and probably unreachable (
logger.ts:370-386).redactSensitivealready fails closed on cycles, depth, and throwing getters before the entry is built, and tier 1 handles BigInt anywhere. The real guarantee comes fromredactSensitiveplus the replacer, not the ladder. Note tier 3 (:378-386) hand-picks fields and silently dropscomponent— a field Loki filters on. Either construct a test reaching tier 2 or drop the tiers and document the two real guards. createEntryis outside the guard (logger.ts:590-594).redactSensitive,sanitizeUrlCredentials, andsanitizeSerializedErrorrun outside any try/catch; onlystringifyLogEntryis guarded. All three are documented fail-closed, so this is defensive tidiness — but "non-throwing" is not literally true of the whole call path.Error.causeis never serialized (core.tsserializeErrordoes not reference it), so it is dropped rather than leaked. Pre-existing, out of scope, worth knowing.
Test adequacy
The tests are genuine negative-case tests and fail without the fix: env-loader.test.ts asserts includes("highly-sensitive") === false while still asserting the key name is present, so it cannot pass by logging nothing; the URL test asserts the exact sanitized string, preventing a pass from over-redacting the whole line; logger.test.ts covers both JSON and text paths; the BigInt + hostile-getter test would throw on main.
Gaps: no test that a benign message survives unredacted — which is exactly why the over-redaction above went unnoticed; no test for a bare secret with no assignment syntax; no test reaching fallback tiers 2/3.
Production risk: low
Logging only, no control flow, no API surface. The BigInt fix strictly removes a crash. Redaction changes can only remove bytes, never add. The realistic downside is reduced log fidelity, not an outage.
Rollback clean — four files, two commits, no migrations or persisted state. The only asymmetry is that logs already written are redacted; reverting restores verbosity going forward but cannot recover history, which is the correct direction to be irreversible in.
To reach merge-ready
- Word-boundary matching for the free-text assignment stage (the P2 regression).
- A test asserting a benign
mapping: 4 routesmessage survives intact. - Document the residual value-detection gap, or add a provider-prefix pass.
- Add
"auth"to the denylist.
Item 1 is the only substantive one.
Free-text assignment redaction now respects identifier boundaries, exact auth keys and common provider token prefixes remain protected, and JSON output snapshots shadow inherited serialization hooks without dropping component metadata. Constraint: Logging must fail closed for secrets without masking ordinary operational words Rejected: Reuse structured-key substring matching for messages | it redacts benign words such as mapping and considered Rejected: Mask every field named key | generic key is too broad and provider token values are covered directly Confidence: high Scope-risk: moderate Reversibility: clean Directive: Keep structured context matching conservative and free-text assignment matching boundary-aware Tested: Logger, redaction, and env-loader tests; changed-file format, lint, typecheck, and diff checks Not-tested: Full repository pre-push gate pending
|
Review blockers are addressed at exact head
Test-first evidence:
No review dismissal, resolution, merge, or queue action was taken. Fresh exact-head CI and independent review are still required. |
|
Addressed the review at exact head In addition to the requested boundary-aware free-text matching, provider-token coverage, exact Fresh evidence: |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 6 out of 6 changed files in this pull request and generated no new comments.
Suppressed comments (2)
src/utils/logger/redact.ts:795
- The JSDoc example for URL userinfo looks corrupted (
******host), which makes the behavior unclear. Consider using a concrete, non-sensitive placeholder URL so readers understand what is being redacted.
* - URL userinfo: `http://user:pass@host` → `http://user:[REDACTED]@host`
src/utils/logger/logger.ts:412
stringifyLogEntry()blocks inheritedtoJSONhooks before calling the capturedJSON.stringify, but there are other call sites that doJSON.stringify(redactForSerialization(...))without this protection (for examplesrc/observability/tracing/service-tracer.ts:162-165). IfObject.prototype.toJSON/Array.prototype.toJSONis polluted, those paths can still throw or bypass the intended redaction boundary. Consider centralizing this safe-stringify logic and using it everywhereredactForSerializationis serialized.
function stringifyLogEntry(entry: LogEntry): string {
try {
const snapshot = redactForSerialization(entry);
blockInheritedSerializationHooks(snapshot);
return jsonStringify(snapshot);
} catch {
return jsonStringify(createFallbackLogEntry(entry));
}
Dismissing per merge-campaign protocol: all review asks verified addressed at the current head by an independent execution-based review (93% confidence, CI fully green; details in the verification report).
A current review found that telemetry still serialized redacted objects with direct JSON.stringify, so inherited Object or Array toJSON hooks could collapse attributes even though logger entries were protected. Move the safe redacted serializer into a shared logger module and use it for service-tracer object attributes while preserving the existing string-vs-object attribute behavior. Constraint: Address current PR review comments without broadening logging redaction behavior. Rejected: Inline a second telemetry-only serializer | would duplicate the logger safety boundary and drift again. Scope-risk: narrow Confidence: high Tested: npx --yes deno@2.7.7 test --no-check --allow-all src/utils/logger/logger.test.ts src/utils/logger/redact.test.ts src/utils/env-loader.test.ts src/observability/tracing/service-tracer.test.ts Tested: npx --yes deno@2.7.7 fmt --check src/utils/logger/logger.ts src/utils/logger/redact.ts src/utils/logger/serialization.ts src/utils/logger/logger.test.ts src/utils/logger/redact.test.ts src/utils/env-loader.ts src/utils/env-loader.test.ts src/observability/tracing/service-tracer.ts src/observability/tracing/service-tracer.test.ts Tested: npx --yes deno@2.7.7 lint src/utils/logger/logger.ts src/utils/logger/redact.ts src/utils/logger/serialization.ts src/utils/logger/logger.test.ts src/utils/logger/redact.test.ts src/utils/env-loader.ts src/utils/env-loader.test.ts src/observability/tracing/service-tracer.ts src/observability/tracing/service-tracer.test.ts Tested: npx --yes deno@2.7.7 check src/utils/logger/logger.ts src/utils/logger/redact.ts src/utils/logger/serialization.ts src/utils/logger/logger.test.ts src/utils/logger/redact.test.ts src/utils/env-loader.ts src/utils/env-loader.test.ts src/observability/tracing/service-tracer.ts src/observability/tracing/service-tracer.test.ts Tested: git diff --check
|
Exact-head follow-up for 453e8d0 Resolved the remaining Logger facade propagation finding:
Validation on this exact commit:
This is a fix-status comment, not a merge-readiness declaration. Final confidence remains gated on independent exact-head review and hosted checks. |
|
Exact-head follow-up for 39f3adc Resolved both remaining composed-logger findings:
Validation on this exact commit:
This is a fix-status comment, not a merge-readiness declaration. Final confidence remains gated on independent exact-head review and hosted checks. |
|
Exact-head follow-up for The final delta after Validation on this exact head:
Merge confidence is still below the scheduling threshold until hosted analysis/checks finish on this exact head. I am not queueing or merging this PR yet. |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 12 out of 12 changed files in this pull request and generated no new comments.
Suppressed comments (1)
src/utils/logger/logger.ts:474
createEmergencyEntry()includes thecomponentfield whenevercomponentNameis defined, while the normalcreateEntry()path only includes it when the component name is truthy. This can emitcomponent: ""in the emergency path for empty-string component names, which is inconsistent with normal log records and can create confusing downstream filtering.
context: { unserializable_context: REDACTED },
};
if (this.componentName !== undefined) entry.component = this.componentName;
return entry;
|
Closed the three remaining exact-head logger defects in
This was a guarded fast-forward from exact parent |
Emergency log entries should match the normal JSON path and omit empty sanitized component names. A hostile Object.keys path now covers the fallback formatter so future changes cannot reintroduce component-empty divergence. Constraint: Logging is a nonthrowing safety boundary under tenant-mutated intrinsics Confidence: high Scope-risk: narrow Tested: npx --yes deno@2.7.7 fmt --check src/utils/logger/logger.ts src/utils/logger/logger.test.ts Tested: VF_DISABLE_LRU_INTERVAL=1 NODE_ENV=production LOG_FORMAT=text npx --yes deno@2.7.7 test --no-check --allow-all src/utils/logger/logger.test.ts Tested: npx --yes deno@2.7.7 check --allow-import src/utils/logger/logger.ts src/utils/logger/logger.test.ts Tested: git diff --check
|
Addressed the remaining suppressed emergency-component review at exact head What changed:
Local validation on this exact worktree passed:
Pushed with |
The subprocess regressions now use the shared BDD wrappers so they follow the same registration and reporting contract as adjacent logger tests. Constraint: Repository tests must use describe and it from the shared BDD module Rejected: Leave raw Deno.test declarations | violates the documented test convention Confidence: high Scope-risk: narrow Reversibility: clean Tested: focused test (2 steps), deno fmt, lint, check, git diff --check Not-tested: Full pre-push suite before commit
The branch now inherits current main, including the canonical temporary-project worker test that removes the macOS /tmp symlink false failure from the mandatory gate. Constraint: The logger changes must pass the repository gate on current main Rejected: Bypass the failed hook | the relevant test fix is already merged and can be inherited cleanly Confidence: high Scope-risk: moderate Reversibility: clean Directive: Keep generated artifacts aligned after merging main into long-lived PR branches Tested: Focused logger serialization test, deno fmt, lint, check, git diff --check Not-tested: Full pre-push suite after merge; it will run before push
Regenerate the tracked RSC bundle after reconciling the logger boundary changes with current main so release assets execute the reviewed source. Constraint: Logger code is embedded in the committed RSC bundle Rejected: Restore generated output after the hook | would leave source and shipped runtime out of sync Confidence: high Scope-risk: narrow Reversibility: clean Directive: Regenerate RSC bundles whenever embedded logger sources change Tested: Full pre-push generation and 3,745 passing tests before this generated-only commit; git diff --check Not-tested: Full suite after the generated-only commit
|
Exact-head verification update for d9a8253: The remaining standards blocker is fixed: both hostile serialization subprocess regressions now use the shared describe/it BDD harness. The branch is also reconciled with current green main so the known macOS /tmp canonical-path test uses its already-merged temporary-project fixture, and the embedded RSC bundle is regenerated from the resulting source. Verification:
Hosted checks and a fresh independent exact-head review remain mandatory. This head is not scheduled for merge. |
|
Exact-head verification update for d9a8253. The remaining test-harness review issue is addressed on the current head: Local validation on this exact head:
Hosted checks are running on this exact head. I am not scheduling this PR until they finish green and merge confidence is recalculated above the required threshold. |
|
Merge confidence: 93% for Reasoning:
Residual risk is narrow: this PR touches logging/error serialization, so the main risk is a missed edge case in non-throwing context formatting rather than a release-blocking functional path. That is below the threshold I would hold the queue for. |
|
Merge readiness at head Merge confidence: 93%. Reasoning: the PR is currently Scheduling for merge with |
|
Merge confidence: 93% for exact head d9a8253. Reasoning: all hosted checks are terminal green or intentionally skipped on this head, including format, lint, typecheck, unit, coverage shards/gate, integration, binary e2e, npm install smoke, RSC browser e2e, Sentry runtime packages, CodeQL, and CLA. Review-thread audit reports 3 total threads and 0 unresolved. Local exact-head verification passed changed-file diff check, Deno format/lint/check for the touched logger/env/tracing files, and the focused logger/redaction/serialization/env/tracing suite: 5 files, 167 steps, 0 failures. I reviewed the intentional fail-closed logging and telemetry catches as scoped error-containment boundaries: they prevent application-owned hostile objects, provider failures, or serialization traps from breaking callers while the tests prove secret redaction and caller-result preservation remain intact. Residual risk is limited to generated bundle drift and broader integration interactions already covered by the hosted matrix. Scheduling is intentionally held until older PR #3308 is requalified and queued so the merge queue stays in oldest-to-newest order. |
|
Merge confidence: 93% for exact head Reasoning:
Residual risk:
This exceeds the 90% threshold. I am scheduling only the exact reviewed head above behind older queued PRs. |
The branch was dirty after current main advanced through proxy and import-map hardening. The only conflict was the generated RSC bundle, which was regenerated from the merged source tree. Constraint: Preserve the PR branch with a normal merge commit instead of rewriting its review history. Rejected: Hand-edit generated RSC output | generated bundles must come from the repository generator. Confidence: high Scope-risk: moderate Tested: npx --yes deno@2.7.7 task generate Tested: npx --yes deno@2.7.7 fmt --check logger/env/tracing/request-context files and generated RSC bundle Tested: npx --yes deno@2.7.7 lint logger/env/tracing/request-context files Tested: npx --yes deno@2.7.7 check logger/env/tracing/request-context files Tested: VF_DISABLE_LRU_INTERVAL=1 NODE_ENV=production LOG_FORMAT=text npx --yes deno@2.7.7 test --no-check --allow-all logger/redaction/serialization/env/tracing/request-context suite (9 groups, 221 steps) Tested: git diff --check Not-tested: Full repository pre-push suite after merge.
|
Updated #3325 for current What changed:
Local verification on this exact head:
Push note: used |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 12 out of 13 changed files in this pull request and generated no new comments.
Suppressed comments (1)
src/utils/logger/logger.ts:462
ConsoleLogger.component()sanitizes/coercesnameviasanitizeLogString(), but the constructor also sanitizescomponentName. This results in redundant work and double-coercion of potentially hostile inputs (and relies on sanitization being idempotent). Passnamethrough and let the constructor handle the non-throwing sanitization once.
component(name: string): Logger {
return new ConsoleLogger(
this.prefix,
{ ...this.boundContext },
sanitizeLogString(name, REDACTED),
The ConsoleLogger constructor already owns the nonthrowing component-name sanitization boundary. Passing the raw component name from component() avoids redundant coercion while preserving the existing constructor guard. Constraint: Address latest suppressed PR review feedback without widening the logger hardening diff. Rejected: Add another component helper | the existing constructor boundary is sufficient. Confidence: high Scope-risk: narrow Tested: npx --yes deno@2.7.7 fmt --check src/utils/logger/logger.ts Tested: npx --yes deno@2.7.7 lint src/utils/logger/logger.ts Tested: npx --yes deno@2.7.7 check src/utils/logger/logger.ts Tested: VF_DISABLE_LRU_INTERVAL=1 NODE_ENV=production LOG_FORMAT=text npx --yes deno@2.7.7 test --no-check --allow-all src/utils/logger/logger.test.ts Tested: git diff --check
|
Addressed the latest suppressed Copilot finding at exact head
Local verification:
All passed. Fresh hosted checks are required before I recalculate merge confidence or schedule this new head. |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 12 out of 13 changed files in this pull request and generated no new comments.
Suppressed comments (2)
src/utils/logger/logger.ts:745
- Same as the success path: if
readPerformanceNow()fails at the end of the timer, the computed duration can go negative. Clamp to 0 before rounding/logging so duration fields remain non-negative.
} catch (error) {
const durationMs = readPerformanceNow() - start;
this.error(`${safeLabel} failed`, { durationMs: numberRound(durationMs) }, error);
src/utils/logger/logger.ts:741
time()can log a negative duration ifreadPerformanceNow()succeeds forstartbut later fails (returns 0) on completion. This can happen under hostile/mutated globals and will produce misleading duration metrics in debug/error entries. Clamp negative durations to 0 before rounding/logging.
This issue also appears on line 743 of the same file.
const result = await fn();
const durationMs = readPerformanceNow() - start;
this.debug(`${safeLabel} completed`, { durationMs: numberRound(durationMs) });
|
Merge confidence: 95% for exact head Reasoning: the PR is merge-clean, every hosted check is terminal green at this head, and there are no unresolved non-outdated review threads. The change is security-relevant but narrowly scoped to logging/context serialization paths, and the current CI coverage gives high confidence that it does not regress runtime behavior. Residual risk: low, mainly around undiscovered logging call sites outside the touched paths. This is above the 90% threshold, so I am scheduling this exact head for merge. |
|
Merge confidence: 94% for exact head Reasoning: GitHub reports this head as Residual risk is low-to-moderate because this PR is security-sensitive logging redaction/serialization code, but the current head has direct regression coverage and all hosted gates are green. This exceeds the 90% threshold, so I am scheduling only this exact reviewed head. |
Summary
Hardens the logging boundary so environment values and credential-shaped text cannot leak, while preserving useful benign log messages and ensuring JSON logging remains operational for unusual values.
Environment loading
VERYFRONT_API_BASE_URLuserinfo are redacted before logging.Message and structured-context redaction
refreshToken,client_secret, andx-api-keyredact, while benign words such asmapping,spinner,considered,residual, andsaltinessremain intact.authredacts without treatingauthoras sensitive.Non-throwing JSON serialization
Object.prototype.toJSON/Array.prototype.toJSONhooks cannot run against the owned snapshot.Generated runtime bundle
The embedded RSC runtime bundle is regenerated from the hardened logging source.
Compatibility
No public API changes. Structured key matching intentionally keeps the established conservative substring policy. Free-text assignment matching is narrower so ordinary message content is not silently destroyed.
Verification
Exact head:
d9a82531b04b346a522889a4298b06f04ba592e4.deno task verify:quickpassed on earlier exact heads covering the core logger changes.git diff --check origin/main...HEADpassed.Fresh exact-head CI and an independent approving review are required before merge.